package xin.petstore.framework.orm.mybatis;

import xin.petstore.framework.annotation.ParaEncryption;
import xin.petstore.framework.annotation.*;
import xin.petstore.framework.cipher.Encryptor;
import xin.petstore.framework.cipher.Md5PasswordEncoder;
import org.apache.commons.beanutils.BeanUtilsBean;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Properties;

@Intercepts({
        @org.apache.ibatis.plugin.Signature(type = org.apache.ibatis.executor.Executor.class, method = "update", args = {
                MappedStatement.class, Object.class}),
        @org.apache.ibatis.plugin.Signature(type = org.apache.ibatis.executor.Executor.class, method = "query", args = {
                MappedStatement.class, Object.class,
                org.apache.ibatis.session.RowBounds.class,
                org.apache.ibatis.session.ResultHandler.class})})
public class EncryptionAndDecryptionInterceptor implements Interceptor {
    private static final Logger logger = LoggerFactory
            .getLogger(EncryptionAndDecryptionInterceptor.class);
    private static final String SECRET_KEY = "encryptKey";
    private static final String SECRET_IMP = "encryptImp";
    private Encryptor encryptor;

    public EncryptionAndDecryptionInterceptor() {
        this.encryptor = null;
    }

    public void setEncryptor(Encryptor encryptor) {
        this.encryptor = encryptor;
    }

    @Override
    @SuppressWarnings({"rawtypes", "unchecked"})
    public Object intercept(Invocation invocation) throws Throwable {
        Object parameter;
        MappedStatement mapperStatement;
        String[] fields;
        String[] arr$;
        int len$;
        int i$;
        String field;
        String s;
        Method get;
        Method set;
        String value;
        String methodName = invocation.getMethod().getName();
        Object returnValue = null;
        if (methodName.equals("query")) {
            Class p_c;
            mapperStatement = (MappedStatement) invocation.getArgs()[0];
            parameter = invocation.getArgs()[1];

            if (null != parameter) {
                p_c = parameter.getClass();
                Encryption e = (Encryption) p_c.getAnnotation(Encryption.class);
                WebDecryption wd = (WebDecryption) p_c.getAnnotation(WebDecryption.class);

                String mapperMethodId = mapperStatement.getId();
                Method daoMethod = this.getMethodByMapperMethodId(mapperMethodId);

                if (null != e) {
                    fields = e.value();
                    arr$ = fields;
                    len$ = arr$.length;
                    for (i$ = 0; i$ < len$; ++i$) {
                        field = arr$[i$];
                        s = field.substring(0, 1).toUpperCase() + field.substring(1);

                        get = p_c.getMethod("get" + s, null);
                        set = p_c.getMethod("set" + s, new Class[]{get.getReturnType()});

                        value = (String) get.invoke(parameter, null);
                        if (value != null)
                            set.invoke(parameter, new Object[]{this.encryptor.encrypt(value)});
                    }
                }
                if (null != wd) {
                    fields = e.value();
                    arr$ = fields;
                    len$ = arr$.length;
                    for (i$ = 0; i$ < len$; ++i$) {
                        //String s;
                        field = arr$[i$];
                        String[] f = field.split("|");
                        if (f.length > 1) {
                            s = f[0].substring(0, 1).toUpperCase() + f[0].substring(1);

                            String _s = f[1].substring(0, 1).toUpperCase() + f[1].substring(1);

                            get = p_c.getMethod("get" + s, null);
                            Method _get = p_c.getMethod("get" + _s, null);
                            set = p_c.getMethod("set" + _s, new Class[]{_get.getReturnType()});

                            value = (String) get.invoke(parameter, null);
                            if (value != null) {
                                String va = this.encryptor.decrypt(value);
                                if (_get.getReturnType().getSimpleName().equalsIgnoreCase("long"))
                                    set.invoke(parameter, new Object[]{Long.valueOf(Long.parseLong(va))});
                                else if ((_get.getReturnType().getSimpleName().equalsIgnoreCase("int"))
                                        || (get.getReturnType().getSimpleName().equalsIgnoreCase("integer")))
                                    set.invoke(parameter,
                                            new Object[]{Integer.valueOf(Integer.parseInt(va))});
                                else if (_get.getReturnType().getSimpleName().equalsIgnoreCase("double"))
                                    set.invoke(parameter, new Object[]{Double.valueOf(Double.parseDouble(va))});
                                else
                                    set.invoke(parameter, new Object[]{Integer.valueOf(Integer.parseInt(va))});
                            }
                        } else {
                            s = field.substring(0, 1).toUpperCase() + field.substring(1);

                            get = p_c.getMethod("get" + s, null);
                            set = p_c.getMethod("set" + s, new Class[]{get.getReturnType()});

                            value = (String) get.invoke(parameter, null);
                            if (value != null)
                                set.invoke(parameter, new Object[]{this.encryptor.decrypt(value)});
                        }
                    }
                }
                if (daoMethod != null && daoMethod.getAnnotation(ParaEncryption.class) != null) {
                    String[] daoMethodParas = daoMethod.getAnnotation(ParaEncryption.class).value();
                    if (parameter instanceof String) {
                        parameter = this.encryptor.encrypt((String) parameter);
                        invocation.getArgs()[1] = parameter;
                    } else {
                        for (int i = 0; i < daoMethodParas.length; i++) {
                            ((HashMap) parameter).put(daoMethodParas[i], this.encryptor.encrypt((String) ((HashMap) parameter).get(daoMethodParas[i])));
                        }
                    }
                    //Invocation newInvocation = new Invocation(invocation.getTarget(), invocation.getMethod(), invocation.getArgs());
                    //returnValue = newInvocation.proceed();
                }
            }
            returnValue = invocation.proceed();

            if (returnValue instanceof List) {
                List emp_list = (List) returnValue;
                int len = emp_list.size();
                if (1 == len && null == emp_list.get(0)) {
                    //do nothing
                } else if (len > 0) {
                    List emp;
                    //String[] fields;
                    int i;
                    Object o;
                    //String[] arr$;
                    //int len$;
                    //int i$;
                    //String field;
                    Class c = emp_list.get(0).getClass();
                    Decryption dn = (Decryption) c.getAnnotation(Decryption.class);
                    WebEncryption we = (WebEncryption) c.getAnnotation(WebEncryption.class);
                    if (we != null) {
                        emp = new ArrayList();
                        fields = we.value();
                        for (i = 0; i < len; ++i) {
                            o = emp_list.get(i);
                            arr$ = fields;
                            len$ = arr$.length;
                            for (i$ = 0; i$ < len$; ++i$) {
                                field = arr$[i$];
                                String[] f = field.split("|");
                                s = f[0].substring(0, 1).toUpperCase() + f[0].substring(1);

                                String _s = f[1].substring(0, 1).toUpperCase() + f[1].substring(1);

                                get = c.getMethod("get" + s, null);
                                set = c.getMethod("set" + _s, new Class[]{get.getReturnType()});

                                value = (String) get.invoke(o, null);
                                if ((value != null) && (null != this.encryptor))
                                    set.invoke(o, new Object[]{this.encryptor.decrypt(value)});
                            }
                            emp.add(o);
                        }
                        returnValue = emp;
                    }
                    if (null != dn) {
                        emp = new ArrayList();
                        fields = dn.value();
                        for (i = 0; i < len; ++i) {
                            o = emp_list.get(i);
                            arr$ = fields;
                            len$ = arr$.length;
                            for (i$ = 0; i$ < len$; ++i$) {
                                field = arr$[i$];
                                s = field.substring(0, 1).toUpperCase() + field.substring(1);

                                get = c.getMethod("get" + s, null);
                                set = c.getMethod("set" + s, new Class[]{get.getReturnType()});

                                value = (String) get.invoke(o, null);
                                if ((value != null) && (null != this.encryptor))
                                    set.invoke(o, new Object[]{this.encryptor.decrypt(value)});
                            }
                            emp.add(o);
                        }
                        returnValue = emp;
                    }
                }
            }
        } else if (methodName.equals("update")) {
            parameter = invocation.getArgs()[1];
            Class c = parameter.getClass();
            Encryption en = (Encryption) c.getAnnotation(Encryption.class);
            Md5Password mp = (Md5Password) c.getAnnotation(Md5Password.class);
            if (en != null) {
                fields = en.value();
                arr$ = fields;
                len$ = arr$.length;
                for (i$ = 0; i$ < len$; ++i$) {
                    field = arr$[i$];
                    s = field.substring(0, 1).toUpperCase() + field.substring(1);

                    get = c.getMethod("get" + s, null);
                    set = c.getMethod("set" + s, new Class[]{get.getReturnType()});
                    value = (String) get.invoke(parameter, null);
                    if ((value != null) && (null != this.encryptor))
                        set.invoke(parameter, new Object[]{this.encryptor.encrypt(value)});
                }
            }
            if (mp != null) {
                String[] password = mp.password();
                String[] salt = mp.salt();
                for (int j = 0; j < password.length; ++j) {
                    String pass = BeanUtilsBean.getInstance().getSimpleProperty(parameter, password[j]);
                    String _salt = BeanUtilsBean.getInstance().getSimpleProperty(parameter, salt[j]);
                    if ((StringUtils.isNotEmpty(pass)) && (StringUtils.isNotEmpty(_salt))) {
                        Md5PasswordEncoder mpe = new Md5PasswordEncoder();
                        pass = mpe.encodePassword(pass, _salt);
                        BeanUtilsBean.getInstance().setProperty(parameter, password[j], pass);
                    }
                }
            }
            returnValue = invocation.proceed();
            //update之后把加密的字段再重新解密
            if (en != null) {
                fields = en.value();
                arr$ = fields;
                len$ = arr$.length;
                for (i$ = 0; i$ < len$; ++i$) {
                    field = arr$[i$];
                    s = field.substring(0, 1).toUpperCase() + field.substring(1);

                    get = c.getMethod("get" + s, null);
                    set = c.getMethod("set" + s, new Class[]{get.getReturnType()});
                    value = (String) get.invoke(parameter, null);
                    if ((value != null) && (null != this.encryptor))
                        set.invoke(parameter, new Object[]{this.encryptor.decrypt(value)});
                }
            }

        } else {
            returnValue = invocation.proceed();
        }
        return returnValue;
    }

    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    public void setProperties(Properties properties) {
        String imp = properties.getProperty("encryptImp");
        try {
            Class cls = Class.forName(imp);
            this.encryptor = ((Encryptor) cls.newInstance());
        } catch (Exception e) {
            logger.error("Initialization encryption error!\n", e);
        }
        this.encryptor.setPassword(properties.getProperty("encryptKey"));
    }

    private Method getMethodByMapperMethodId(String mapperMethodId) {
        String daoClassName = mapperMethodId.substring(0, mapperMethodId.lastIndexOf("."));
        String daoMethodName = mapperMethodId.substring(mapperMethodId.lastIndexOf(".") + 1);
        try {
            Class<?> daoClass = Class.forName(daoClassName);
            for (Method tempMethod : daoClass.getMethods()) {
                if (tempMethod.getName().equals(daoMethodName)) {
                    return tempMethod;
                }
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        return null;
    }
}